package edu.virginia.cs4501.team22;

import java.util.ArrayList;
import java.util.List;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;

public class Map extends MapActivity {
	public boolean doUpdates;
	private LocationManager locMan;
	private String locProv;
	private MyLocationListener myLocListener;
	private ClassFinderApp application;
	private MapController myMapController;
	private MapView mapView;

	private ArrayList<Pair<GeoPoint, GeoPoint> > myRoute;

	protected Drawable floorplan;

	protected GeoPoint origin = null;
	protected GeoPoint destination = null;
	protected float distanceBetween = 21.0F;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.map);
		mapView = (MapView) findViewById(R.id.mapview);
		mapView.setBuiltInZoomControls(true);

		List<Overlay> listOfOverlays = mapView.getOverlays();
		listOfOverlays.clear();
		listOfOverlays.add(new MyLocationOverlay());
		listOfOverlays.add(new MyDestinationOverlay());
		listOfOverlays.add(new MyRouteOverlay());

		application = (ClassFinderApp)getApplicationContext();

		myMapController = mapView.getController();
		myMapController.setZoom(16);

		myLocListener = new MyLocationListener();

		locMan = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
		updateLocationManager();

		myRoute = new ArrayList<Pair<GeoPoint, GeoPoint> >();

		new GetCoordinateTask().execute(application.getEventNow().getBuildingName());

		doUpdates = true;
		updateView();
	}

	private void updateLocationManager() {
		final long MINIMUM_DISTANCECHANGE_FOR_UPDATE = 25; // in Meters
		final long MINIMUM_TIME_BETWEEN_UPDATE = 5000; // in Milliseconds

		locProv = locMan.getAllProviders().get(0);//get best provider
		locMan.requestLocationUpdates(locProv,
				MINIMUM_TIME_BETWEEN_UPDATE,//pre-defined time
				MINIMUM_DISTANCECHANGE_FOR_UPDATE,//pre-defined distance
				myLocListener);//my listener
	}

	@Override
	public void onPause() {
		doUpdates = false;
		locMan.removeUpdates(myLocListener);
		super.onPause();
	}

	@Override
	public void onResume() {
		super.onResume();
		doUpdates = true;
		updateLocationManager();
		destination = null;
		new GetCoordinateTask().execute(application.getEventNow().getBuildingName());
		updateView();
	}

	@Override
	public void onDestroy() {
		locMan = null;
		myLocListener = null;
		super.onDestroy();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.map_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.next_class:
			try {
				application.incrementSchedule();
				new GetCoordinateTask().execute(application.getEventNow().getBuildingName());
			} catch (IndexOutOfBoundsException e) {
				Toast.makeText(application, "Reached end of your schedule..", Toast.LENGTH_SHORT);
				//if we reach here, no need to update destination point :-P
			}
			updateView();
			break;
		case R.id.go_to:
			//TODO popup
		case R.id.edit_schedule:
			break;
		case R.id.previous_class:
			try {
				application.decrementSchedule();
				new GetCoordinateTask().execute(application.getEventNow().getBuildingName());
			} catch (IndexOutOfBoundsException e) {
				Toast.makeText(application, "Reached end of your schedule..", Toast.LENGTH_SHORT);
				//if we reach here, no need to update destination point :-P
			}
			updateView();
			break;
		}
		return true;
	}

	public void updateView() {
		Location myLocation;
		try {
			myLocation = locMan.getLastKnownLocation(locProv);
			if (myLocation == null)
				throw new Exception();
		} catch (Exception e) {
			myLocation = new Location(locProv);
			myLocation.setLatitude(38.050000);
			myLocation.setLongitude(-78.510000);
		}
		//solve GeoPoints
		origin = new GeoPoint((int)(myLocation.getLatitude()*1E6), (int)(myLocation.getLongitude()*1E6));
		myMapController.animateTo(origin);
		this.mapView.invalidate();
	}

	class MyLocationOverlay extends Overlay {
		@Override
		public void draw(Canvas canvas, MapView map, boolean shadow) {
			Projection projection = map.getProjection();

			Point p = new Point();
			projection.toPixels(origin, p);

			RectF mapPoint = new RectF(p.x-5, p.y+5, p.x+5, p.y-5);

			Paint myPaint = new Paint();
			myPaint.setStyle(Style.FILL);
			myPaint.setARGB(80, 156, 192, 36);
			myPaint.setAntiAlias(true);

			canvas.drawOval(mapPoint, myPaint);
			super.draw(canvas, map, shadow);

		}
	}

	//We use three types of overlay for more flexibility in what we do here
	class MyDestinationOverlay extends Overlay {
		@Override
		public void draw(Canvas canvas, MapView map, boolean shadow) {
			if (destination != null) {
				Paint myPaint = new Paint();
				Projection projection = map.getProjection();
				Point p = new Point();

				projection.toPixels(destination, p);
				//				Toast.makeText(application, p.toString(), Toast.LENGTH_SHORT).show();

				RectF mapPoint = new RectF(p.x-5, p.y+5, p.x+5, p.y-5);

				myPaint.setStyle(Style.FILL);
				myPaint.setARGB(80, 156, 192, 36);
				myPaint.setAntiAlias(true);

				canvas.drawOval(mapPoint, myPaint);
				super.draw(canvas, map, shadow);
			}
		}
	}
	/**TODO get pictures
	 * 1. construct name for picture
	 * 2. send name to function
	 * FUNCTION
	 * 		[list of all constants]
	 * switch(name)
	 * compare to all known images and return the resources get R.drawable.CONSTANT_WE_KNOW
	 * default, a picture: we are sorry but currently we haven't the access to the image you need
	 *
	 *my complex type to store where a pair is <start, end> for each leg of a route
		Pair<GeoPoint, GeoPoint> j = new Pair<GeoPoint, GeoPoint>(origin, destination);
		ArrayList<Pair<GeoPoint, GeoPoint> > myList = new ArrayList<Pair<GeoPoint, GeoPoint> >();
		myList.add(j);
	 */

	class MyRouteOverlay extends Overlay {
		@Override
		public void draw(Canvas canvas, MapView map, boolean shadow) {
			if (myRoute.size()>0) {//don't bother going any further if there isn't a route to display
				Projection projection = map.getProjection();
				Paint myPaint = new Paint();
				Path myPath = new Path();
				Point p = new Point(); //reusable point for the path-making

				projection.toPixels(myRoute.get(0).first, p);//the beginning of the route (our origin)
				Toast.makeText(application, "From: " + p.toString(), Toast.LENGTH_SHORT);
				myPath.moveTo(p.x, p.y);//set-up the path from our location
				for(Pair<GeoPoint, GeoPoint> leg : myRoute) {//build the path based on the route
					projection.toPixels(leg.second, p);
					myPath.lineTo(p.x, p.y);
					Toast.makeText(application, "to: " + p.toString(), Toast.LENGTH_SHORT);
				}

				Toast.makeText(application, "Final: " + myPath.toString(), Toast.LENGTH_SHORT);
				//set up the painter for the path
				myPaint.setStyle(Style.STROKE);
				myPaint.setARGB(80, 156, 192, 36);
				myPaint.setStrokeWidth(3);
				myPaint.setAntiAlias(true);

				canvas.drawPath(myPath, myPaint);
				super.draw(canvas, map, shadow);
			}
		}
		/**
		 * TODO
		 * 1. not within the proximity of the destination
		 * 		draw a dynamic route using Google Maps
		 * 2. within the building's proximity
		 * 		draw a static route to (or just highlight) the room
		 */
	}

	class MyLocationListener implements LocationListener {
		@Override
		public void onLocationChanged(Location myLocation) {
			origin = new GeoPoint((int)(myLocation.getLatitude()*1E6), (int)(myLocation.getLongitude()*1E6));
			if (doUpdates)
				Map.this.updateView();
		}
		@Override
		public void onProviderDisabled(String provider) {
			Map.this.locProv = Map.this.locMan.getAllProviders().get(0);
		}
		@Override
		public void onProviderEnabled(String provider) {
			Map.this.locProv = Map.this.locMan.getAllProviders().get(0);
		}
		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
		}
	}

	@Override
	protected boolean isRouteDisplayed() {
		//if we have any overlays, we are displaying a 'route' (excluding user position)
		return !(mapView.getOverlays().size() <= 1);
	};

	//TODO Changed, but untested
	private class GetDirectionsTask extends AsyncTask<Pair<GeoPoint, GeoPoint>, Void, String> {

		private static final String GD_METHOD_NAME = "directionsJSON";
		private static final String GD_SOAP_ACTION = "http://stardock.cs.virginia.edu:8080/Team22_Services_r3/services/GetDirectionsPort";
		private static final String GD_NAMESPACE = "http://services.team22.cs4501.virginia.edu/";
		private static final String GD_URL = "http://stardock.cs.virginia.edu:8080/Team22_Services_r3/services/GetDirectionsPort?wsdl";

		@Override
		protected String doInBackground(Pair<GeoPoint, GeoPoint>... coords) {
			String sendString = coords[0].first.toString() + "," +coords[0].second.toString();

			SoapObject request = new SoapObject(GD_NAMESPACE, GD_METHOD_NAME);
			request.addProperty("myLatLongYourLatLong", sendString);

			SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
			envelope.setOutputSoapObject(request);
			HttpTransportSE androidHttpTransport = new HttpTransportSE(GD_URL);

			try {
				androidHttpTransport.call(GD_SOAP_ACTION, envelope);
				return envelope.getResponse().toString();
			} catch (Throwable t) {
				return "Error retrieving directions to destination: " + t.toString();
			}
		}

		@Override
		protected void onPostExecute(String result) {
			myRoute.clear();
			myRoute.add(new Pair<GeoPoint, GeoPoint>(origin, destination));
			updateView();
		}

	}

	private class GetCoordinateTask extends AsyncTask<String, Void, GeoPoint> {

		private static final String GC_METHOD_NAME = "getCoordinate";
		private static final String GC_SOAP_ACTION = "http://stardock.cs.virginia.edu:8080/Team22_Services_r3/services/BuildingFinderPort";
		private static final String GC_NAMESPACE = "http://services.team22.cs4501.virginia.edu/";
		private static final String GC_WSDL_URL = "http://stardock.cs.virginia.edu:8080/Team22_Services_r3/services/BuildingFinderPort?wsdl";

		@Override
		protected GeoPoint doInBackground(String... buildingName) {
			String ret = "";
			SoapObject request = new SoapObject(GC_NAMESPACE, GC_METHOD_NAME);
			request.addProperty("buildingName", buildingName[0]);

			SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
			envelope.setOutputSoapObject(request);
			HttpTransportSE androidHttpTransport = new HttpTransportSE(GC_WSDL_URL);

			try {
				androidHttpTransport.call(GC_SOAP_ACTION, envelope);
				ret = envelope.getResponse().toString();
			} catch (Throwable t) {
			}
			float[] coords = {0.0F, 0.0F};
			coords[0] = Float.parseFloat(ret.substring(0, ret.indexOf(',')));//x-long
			coords[1] = Float.parseFloat(ret.substring(ret.indexOf(',')+1, ret.length()-1));//y-lat

			//create the point and add it to the cache
			GeoPoint point = new GeoPoint((int)(coords[1]*1E6),(int)(coords[0]*1E6));
			//			buildingLocationCache.put(buildingName, point);

			//GeoPoints use micro degrees; it's the same thing, just times 10^6 and stored as an int.
			return point;
		}

		@Override
		protected void onPostExecute(GeoPoint result) {
			destination = result;
			updateView();
			new GetDirectionsTask().execute(new Pair<GeoPoint, GeoPoint>(origin, result));
		}

	}
}